package com.wkbb.sports.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.wkbb.sports.service.WorkService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author lbh
 * @date 2021年04月19日 20:58:33
 * @description: @ServerEndpoint 注解是一个类层次的注解，它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到webSocket服务器端
 * @ServerEndpoint 注解声明websocket请求路径的同时，还标记该类为一个原型模式的类，而不是单例模式的类，即每一个连接建立起来都会重新实例化一个新的实例。所以
 * 被@Autowired注解注入的实例Bean注入操作会被直接略过，必须要借助于SpringBeanUtil或者SpringUtils工具类来手动注入一个全局变量，如aService。
 *
 * 注意：
 *方法一【不推荐】 configurator属性是为了解决websocket下实例无法注入问题，但是它让WebSocketServer类里面的webSocketSet.size()的值不管有多少个不同的客户端连接始终为1，导致无法广播群发消息推到客户端，所以不推荐，原因未知，但是可以
 * 通过onOpen()方法的@PathParam路径参数注解配合@ServerEndpoint注解的路径参数/{sid}来曲线实现，其中sid是前端每次连接时带上一个唯一性的字符串，比如时间戳+随机数，并通过ConcurrentHashMap<String, WebSocketServer>
 * 存储来间接实现，但是亲测貌似也不太准确
 *
 *方法二【推荐】配合【关键A】，既可以准确记录下有多少个不同的客户端连接到服务端，广播群发消息推到客户端，又可以通过SpringBeanUtil工具类来解决在websocket的对象WebSocketServer下实例WorkService无法注入的问题
 *
 * 方法一与方法二主要区别在于@ServerEndpoint注解中是否配置属性configurator = EndpointConfigure.class(自定义的类)来实现解决websocket下实例无法注入问题
 * 当然，如果websocket中无需与数据库产生数据交互，不存在实例注入，那就不用考虑那么多了。
 *
 */
@Component
@Slf4j
@Data
//@ServerEndpoint("/websocket/{sid}/{token}")
//@ServerEndpoint("/websocket/{token}")
//@ServerEndpoint(value="/websocket/{token}",configurator = EndpointConfigure.class)
//@ServerEndpoint(value="/websocket/{sid}",configurator = EndpointConfigure.class) //方式一
//@ServerEndpoint(value="/websocket") //方式二
//@ServerEndpoint(value="/api/sports/v1/websocket") //方式二【目前在用】
//public class WebSocketServer  implements Serializable,Comparable<WebSocketServer>{
//public class WebSocketServer  implements InitializingBean {
public class WebSocketServer {

        //@Autowired
        //private WorkService workService;//测试失败

        //@Autowired
        //private Demo demo;//测试失败

        //如果有与数据库数据交互，解决websocket下实例无法注入问题【关键A】
        //如果有多个同名类的话
        //格式如下：需要在AService接口的实现类AServiceImpl上这样写注解@Service("aService")，而不能是默认的@Service
        //private AService aService = SpringBeanUtil.getBean("aService", AService.class);
        //但是如果仅仅通过类类型来获取则不用指定实例名称，直接在对应类上面加上@Service或者@Component注解即可。

        //private WorkService workService = SpringBeanUtil.getBean("workService", WorkService.class);//测试成功
        //private WorkService workService = SpringBeanUtil.getBean("workService");//测试成功
        //private WorkService workService = SpringBeanUtil.getBean(WorkService.class);//测试成功

        private WorkService workService = SpringUtils.getBean("workService", WorkService.class);//测试成功
        //private WorkService workService = SpringUtils.getBean("workService");//测试成功
        //private WorkService workService = SpringUtils.getBean(WorkService.class);//测试成功

        //private Demo demo = SpringBeanUtil.getBean("demo", Demo.class);//测试成功
        //private Demo demo = SpringUtils.getBean("demo");//测试成功
        //private Demo demo = SpringUtils.getBean(Demo.class);//测试成功

        //当前学生连接数【所有学生共享】
        private volatile static int onlineCount = 0;

        //当前连接学生的作业id
        private String workId;

        //当前连接学生的学生id
        private String studentId;

        //当前连接学生的实时跳绳数量【当前学生独享】
        private int number = 0;

        //当前连接学生的头像地址【当前学生独享】
        private String headImgUrl;

        //当前连接学生的名称【当前学生独享】
        private String studentName;

        //当前连接学生的实时时长【当前学生独享】
        private int duration;

        //心跳检测标志【默认值为0，一旦维持心跳将其值改变为1，定时器会将其定时重置为0，一分钟之内没有再次变为1则说明客户端非正常断开连接，需要将其从webSocketSet中删除掉】
        private int flag=0;

        //存放每个客户端对应的WebSocketServer对象，线程安全的无序的集合，可以将它理解成线程安全的HashSet，可去重
        private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

        //websocket session id -> session【错误】webSocketMap.size()数据不准确
        //sid -> session【前端每次连接时带上一个唯一性的数字，比如时间戳】
        //private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<String, Session>();
        private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<String, WebSocketServer>();

        private Session session;

        //接收sid【前端每次连接时带上一个唯一性的字符串，比如时间戳+随机数】
        private String sid = "";

        //查询出当前作业已经做的学生的成绩【所有学生共享】
        private static CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentListStatic=new CopyOnWriteArrayList<WebSocketInfoDto>();

        /**
         * 定时查询查询出当前作业已经做的学生的成绩
         */
        private static ScheduledExecutorService sensorService;
        private static ScheduledFuture sensorFuture = null;

        /**
         * 定时查询查询出当前作业已经做的学生的成绩
         */
        private ScheduledExecutorService heartBeatService;
        private ScheduledFuture heartBeatFuture = null;

       /* //查询出当前作业已经做的学生的成绩
        private static List<com.wkbb.sports.dto.WebSocketInfoDto> studentScoreOfWorkList;

        //将com.wkbb.sports.dto.WebSocketInfoDto转化为com.wkbb.sports.websocket.WebSocketInfoDto
        private static CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList;

        //初始化
        @Override
        public void afterPropertiesSet() throws Exception {
            studentScoreOfWorkList=workService.getStudentScoreOfWork(workId);
            historyScoreStudentList=WebSocketInfoDto.transferWebSocketInfoDto(studentScoreOfWorkList);
        }*/

        /**
         *连接建立成功调用的方法【用来初始化每一个客户端连接的实例】
         * sid和token参数暂未用到，但是初始化参数一般都是通过onOpen()方法的@PathParam路径参数注解配合@ServerEndpoint注解的路径参数/{sid}来实现的
         * */
        @OnOpen
        public void onOpen(Session session,@PathParam("sid") String sid, @PathParam("token") String token) {
            log.info("===1=onOpen=======webSocketSet.size()============："+webSocketSet.size());
            //log.info("===1=onOpen=======sessionMap.size()============："+sessionMap.size());
            //log.info("===1=onOpen=======webSocketMap.size()============："+webSocketMap.size());
            this.session = session;
            webSocketSet.add(this);//加入set中
            //demo.test();
            //Demo.test1();
            //sessionMap.put(sid,session);
            //webSocketMap.put(sid,this);
            //sessionMap.put(session.getId(),session);
            //log.info("===2=onOpen=======session.getId()============："+session.getId());
            log.info("===2=onOpen=======webSocketSet.size()============："+webSocketSet.size());
            //log.info("===2=onOpen=======sessionMap.size()============："+sessionMap.size());
            //log.info("===2=onOpen=======webSocketMap.size()============："+webSocketMap.size());
            this.sid = sid;
            addOnlineCount();//在线数加1
//          sendMessage("===1111111==========conn_success=======token:"+token);
//            sendMessageSelf(generateMessageStr(WebsocketEnum.CONNECT_SUCCESS,"当前在线人数为"+webSocketSet.size()));//单个发
            sendMessageBatch(generateMessageStr(WebsocketEnum.CONNECT_SUCCESS,"当前在线人数为"+webSocketSet.size()));//群发
            //log.info("===onOpen==getOnlineCount()====222222222222有新窗口开始监听:" + sid +",当前在线人数为:"+ getOnlineCount());
            //log.info("===onOpen======222222222222有新窗口开始监听:" + sid +",当前在线人数为:"+ getOnlineCount()+",webSocketSet.size()当前在线人数为:"+webSocketSet.size());
            log.info("===onOpen======222222222222有新窗口开始监听:" + sid +",当前在线人数为:"+ webSocketSet.size());
            scheduleHeartBeat();
        }

        /**
         *连接关闭调用的方法*/
        @OnClose
        public void onClose(){
            webSocketSet.remove(this);//从set中删除
            //sessionMap.remove(sid);
            //webSocketMap.remove(sid);
            //sessionMap.remove(session.getId());
            //log.info("===3=onClose=======session.getId()============："+session.getId());
            subOnlineCount();//在线数减1
            log.info("==onClose=====44444444444444释放的sid为:"+sid);
            //log.info("==onClose===getOnlineCount()==55555555555555有一连接关闭!当前在线人数为"+ getOnlineCount());
            log.info("===3=onClose=======webSocketSet.size()============："+webSocketSet.size());
            //log.info("===3=onClose=======sessionMap.size()============："+sessionMap.size());
            //log.info("===3=onClose=======webSocketMap.size()============："+webSocketMap.size());

            //log.info("===4=onClose=======webSocketSet.size()============："+webSocketSet.size());
            //log.info("===4=onClose=======sessionMap.size()============："+sessionMap.size());
            log.info("==onClose=====55555555555555有一连接关闭!当前在线人数为"+  webSocketSet.size());
            closeHeartBeat();
        }

        /**
        *收到客户端消息后调用的方法【用来处理每一个客户端连接的业务逻辑】
        * Param message客户端发送过来的消息
         * 前后端交互约定的message数据格式示例值为：{"code":10002,"data":{"heart":123,"num":1,"username":"李四","msg":"你好呀"}}
         * */
        @OnMessage
        public void onMessage(String message,Session session) {
            if(StringUtils.isEmpty(message)){
                sendMessageSelf(generateMessage(WebsocketEnum.ERROR_DATA_RESPONSE, new JSONObject()));
                return;
            }
//            log.info("66666666666收到来自窗口" + sid + "的信息:" + message);
            WebsocketResult websocketResult=getWebsocketResult(message);
            if(websocketResult == null) {
                sendMessageSelf(generateMessage(WebsocketEnum.ERROR_DATA_RESPONSE, new JSONObject()));
                return;
            }
            JSONObject jb=getJb(websocketResult);
            if(jb == null) {
                sendMessageSelf(generateMessage(WebsocketEnum.ERROR_DATA_RESPONSE, new JSONObject()));
                return;
            }
            Integer code=websocketResult.getCode();
            if(WebsocketEnum.VERIFY_TOKEN.getCode().equals(code)){ //token解析校验逻辑，目前省略
                String token=jb.getString("token");
                log.info("====onMessage==============66666666666收到来自窗口token：==================" +token);
                if(StringUtils.isEmpty(token)){
                    sendMessageSelf(generateMessage(WebsocketEnum.VERIFY_TOKEN_ERROR_RESPONSE,new JSONObject()));
                    onClose();
                    return;
                }
                sendMessageSelf(generateMessage(WebsocketEnum.VERIFY_TOKEN_RESPONSE,new JSONObject()));
                return;
            }else if(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM.getCode().equals(code)){ //接收跳绳数量
                log.info("====onMessage==============66666666666收到来自窗口========ACCEPT_JUMP_ROPE_NUM===========");
                //部分业务参数其实可以通过onOpen方法的路径参数一次性初始化的，而不用每次通过onMessage方法传输，比如workId、studentId、studentName这几个几乎不怎么变的参数，但前端嫌麻烦，所以通过onMessage方法传输，
                // 但是实时跳绳数量number以及实时跳绳时长duration等字段必须通过onMessage方法传输,因为它时刻在变。而学生的头像地址headImgUrl值因为带有斜杠/与路径参数容易混淆，所以也必须通过onMessage方法传输。
                this.workId=jb.getString("workId");//当前连接学生的作业id
                this.studentId=jb.getString("studentId");//当前连接学生的学生id
                //this.number++;//后端跳绳数量计数【按道理number++要与jb.getIntValue("num")值一致，但是还是以前端传过来的值为主】
                this.number=jb.getIntValue("number");//前端主动上传的跳绳数量
                this.headImgUrl=jb.getString("headImgUrl");//当前连接学生的头像地址
                this.studentName=jb.getString("studentName");//当前连接学生的名称
                this.duration=jb.getIntValue("duration");//当前连接学生的实时时长

                if(StringUtils.isEmpty(workId)){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_ERROR_RESPONSE,"workId字段不能为空"));
                    return;
                }

                if(StringUtils.isEmpty(studentId)){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_ERROR_RESPONSE,"studentId字段不能为空"));
                    return;
                }

                if(number==0){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_ERROR_RESPONSE,"number字段不能为空"));
                    return;
                }

                if(StringUtils.isEmpty(headImgUrl)){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_ERROR_RESPONSE,"headImgUrl字段不能为空"));
                    return;
                }

                if(StringUtils.isEmpty(studentName)){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_ERROR_RESPONSE,"studentName字段不能为空"));
                    return;
                }

                if(duration==0){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_ERROR_RESPONSE,"duration字段不能为空"));
                    return;
                }

                if(CollectionUtils.isEmpty(historyScoreStudentListStatic)) {
                    log.info("==ACCEPT_JUMP_ROPE_NUM=================初次访问需要初始化查询数据库========================");
                    //查询出当前作业已经做的学生的成绩【一次初始化就好】
                    historyScoreStudentListStatic = geneWebSocketInfoDto();
                }

                /*if(number%20==0){ //避免实时修改数据库压力太大，所以采取批次修改
                }*/
                sendMessageSelf(generateMessage(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_RESPONSE,new JSONObject()));
                //实时广播返回前三名或者含自己的四名给前端的跳绳数量
                broadcastJumpRopeNumber();//发送方式一
                //broadcastJumpRopeNumberMap();//发送方式二
                return;
            }else if(WebsocketEnum.RANK.getCode().equals(code)){ //排名响应
                log.info("====onMessage==============66666666666收到来自窗口========RANK===========");
                JSONObject dataJb=new JSONObject();
                String workId=jb.getString("workId");
                String studentId=jb.getString("studentId");
                if(StringUtils.isEmpty(workId)){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.RANK_ERROR_RESPONSE,"workId字段不能为空"));
                    return;
                }
                this.workId=workId;
                /*if(StringUtils.isEmpty(studentId)){
                    sendMessageSelf(generateMessageStr(WebsocketEnum.RANK_ERROR_RESPONSE,"studentId字段不能为空"));
                    return;
                }
                this.studentId=studentId;
                ResultDTO resultDTO= workService.getStudentPositionOfWorkThird(workId,studentId);
                List<Map<String, Object>> mapList=(List<Map<String, Object>>)resultDTO.getData();
                dataJb.put("mapList",mapList);
                sendMessageSelf(generateMessage(WebsocketEnum.RANK_RESPONSE,dataJb));*/

                if(CollectionUtils.isEmpty(historyScoreStudentListStatic)) {
                    log.info("==RANK=================初次访问需要初始化查询数据库========================");
                    //查询出当前作业已经做的学生的成绩【一次初始化就好】
                    historyScoreStudentListStatic = geneWebSocketInfoDto();
                }

                //学生刚刚打开做作业界面时返回前三名给前端的跳绳数量
                initPushJumpRopeNumber();
                return;

            }else if(WebsocketEnum.HEARTBEAT.getCode().equals(code)){ //心跳检测【前端每隔10秒发送一次该指令】
                //心跳检测标志【默认值为0，一旦维持心跳将其值改变为1，定时器会将其定时重置为0，一分钟之内没有再次变为1则说明客户端非正常断开连接，需要将其从webSocketSet中删除掉】
                this.flag=1;
//                log.info("==================66666666666收到来自窗口========HEARTBEAT===========");
                sendMessageSelf(generateMessage(WebsocketEnum.HEARTBEAT_RESPONSE,new JSONObject()));
                return;

            }else if(WebsocketEnum.RESET.getCode().equals(code)){ //重置【开发时方便调用，上线时屏蔽掉】
                log.info("====onMessage==============66666666666收到来自窗口========重置===========");
                //onClose();
                //重置时批量关闭
                closeBatch();
                sendMessageSelf(generateMessage(WebsocketEnum.RESET,new JSONObject()));
                webSocketSet.clear();
                return;

            }else{ //其余非法命令
                log.info("====onMessage==============66666666666收到来自窗口========other===========");
                sendMessageSelf(generateMessage(WebsocketEnum.ERROR_RESPONSE,new JSONObject()));
//                onClose();
                return;
            }
        }

        /**
         * 心跳检测标志【默认值为0，一旦维持心跳将其值改变为1，定时器会将其定时重置为0，一分钟之内没有再次变为1则说明客户端非正常断开连接，需要将其从webSocketSet中删除掉】
         */
        public void scheduleHeartBeat(){
            heartBeatService = Executors.newSingleThreadScheduledExecutor();
            heartBeatFuture = heartBeatService.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                   if(flag !=1){
                       log.info("===workId为："+workId+"====studentId为："+studentId+"=====studentName为："+studentName+"====心跳断开，准备删除===============");
                       webSocketSet.remove(this);
                       closeHeartBeat();
                   }else{
                       log.info("===workId为："+workId+"====studentId为："+studentId+"=====studentName为："+studentName+"====心跳正常===============");
                       flag=0;
                   }
                }
            }, 60, 90, TimeUnit.SECONDS);
        }

        public void closeHeartBeat(){
            if(heartBeatService != null){
                log.info("=======================Shutting down closeHeartBeat");
                heartBeatService.shutdownNow();
                heartBeatService = null;
            }
        }

        /**
         *Param session
         * Param error
         * */
        @OnError
        public void onError(Session session,Throwable error) {
            log.error("=====onError=====666666666666666666发生错误");
            error.printStackTrace();
        }

        /**实现服务器主动推送*/
        //有的时候消息发送会报异常信息：java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
        //产生该问题的原因是因为异步调用，但没有加锁，导致一个Session正在使用中，又被另一个线程调用；
        //解决方法：
        //方法一：发送方法上添加synchronized 关键字；
        //方法二：将异步发送方法getAsyncRemote()切换成同步发送方法getBasicRemote()
        //也就是异步的方式不会使当前的线程阻塞，导致不能响应其它事件，这就是异步发送方法getAsyncRemote()和同步发送方法getBasicRemote()的区别。
        public synchronized  void sendMessage(String message) throws IOException {
            if(session != null) {
                this.session.getBasicRemote().sendText(message);//同步发送
                //this.session.getAsyncRemote().sendText(message);//异步发送
            }
        }

        /**群发自定义消息*/
        public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException{
                log.info("77777777777777777777777推送消息到窗口"+sid +"，推送内容:"+message);
                for (WebSocketServer item : webSocketSet) {
                    try {
                        /*if (sid == null) { //为nul1则全部推送
                            item.sendMessage(message);
                        } else if (item.sid.equals(sid)) {
                            item.sendMessage(message);
                        }*/
                        item.sendMessage(message);
                    }catch (IOException e) {
                        continue;
                    }
            }
    }

        /**发送自定义消息*/
        public void sendMessageSelf(String message) {
//            log.info("sendMessage" +"==========推送内容:"+message);
                try {
                    sendMessage(message);
                    //群发消息
                /*for (WebSocketController item : webSocketSet) {
                    try {
                        item.sendMessage(message);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }*/
                }catch (IOException e) {
                    e.getMessage();
                }
        }

         public void sendMessageBatch(String message) {
            //群发消息
                for (WebSocketServer item : webSocketSet) {
                    try {
                        item.sendMessage(message);
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                }
        }

        //重置时批量关闭
        public void closeBatch() {
            //群发消息
            for (WebSocketServer item : webSocketSet) {
                    item.onClose();
            }
        }

        /**生成响应消息*/
//    前后端交互约定的responseMessage数据格式示例值为：{"code":10002,"data":{"heart":123,"num":1,"username":"李四","msg":"你好呀"}}
        public String generateMessage(WebsocketEnum websocketEnum,JSONObject dataJb) {
            WebsocketResult websocketResultResponse=new WebsocketResult();
            websocketResultResponse.setCode(websocketEnum.getCode());

            JSONObject jb=new JSONObject();
            jb.put("msg",websocketEnum.getDesc());
            jb.putAll(dataJb);

            //websocketResultResponse.setData(JSON.toJSONString(jb));
            websocketResultResponse.setData(jb);
            String responseMessage=JSON.toJSONString(websocketResultResponse);
            if(!WebsocketEnum.HEARTBEAT.getCode().equals(10003)){
                log.info("generateMessage==========响应内容:"+responseMessage);
            }
            return responseMessage;
        }


        public String generateMessageStr(WebsocketEnum websocketEnum,String msg) {
            WebsocketResult websocketResultResponse=new WebsocketResult();
            websocketResultResponse.setCode(websocketEnum.getCode());

            JSONObject jb=new JSONObject();
            jb.put("msg",msg);

            //websocketResultResponse.setData(JSON.toJSONString(jb));
            websocketResultResponse.setData(jb);
            String responseMessage=JSON.toJSONString(websocketResultResponse);

            if(!WebsocketEnum.HEARTBEAT.getCode().equals(10003)){
                log.info("generateMessageStr==========响应内容:"+responseMessage);
            }
            return responseMessage;
        }


        public JSONObject getJb(WebsocketResult websocketResult){
            Integer code=websocketResult.getCode();
            //String data=websocketResult.getData();
            Object data=websocketResult.getData();
            if(!WebsocketEnum.HEARTBEAT.getCode().equals(10003)){
                log.info("==================66666666666收到来自窗口code：==================" +code);
                log.info("==================66666666666收到来自窗口data：==================" +data);
            }

            JSONObject jb=null;
            try {
                jb=JSONObject.parseObject(String.valueOf(data));
            }catch (Exception e){
                sendMessageSelf(generateMessage(WebsocketEnum.ERROR_DATA_RESPONSE,new JSONObject()));
    //                onClose();
                return null;
            }
            return jb;
        }

        public WebsocketResult getWebsocketResult(String message) {
            WebsocketResult websocketResult = null;
            try {
                websocketResult = JSONObject.parseObject(message, WebsocketResult.class);
            } catch (Exception e) {
                sendMessageSelf(generateMessage(WebsocketEnum.ERROR_DATA_RESPONSE, new JSONObject()));
                return null;
            }
            return websocketResult;
        }

        //方法零：实时广播返回前三名或者含自己的四名给前端的跳绳数量【WebSocketServer】包含了javax.websocket.Session session属性的WebSocketServer对象无法被JSON.toJSONString()化【无法实现】
        /*public void broadcastJumpRopeNumber(){
            if(webSocketSet.size()>1) { //只有当前实时跳绳人数大于1时才需要广播推送
                CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList(webSocketSet);//先将顺序的CopyOnWriteArraySet转化为有序的CopyOnWriteArrayList
                Collections.sort(copyOnWriteArrayList);//再对它进行降序排序
                log.info("=========webSocketSet.size()==============：" + webSocketSet.size());
                JSONObject dataJb = new JSONObject();
                List subList = Lists.newArrayList();

                if (!CollectionUtils.isEmpty(copyOnWriteArrayList) && copyOnWriteArrayList.size() > 3) {
                    subList = copyOnWriteArrayList.subList(0, 3);//再获取前三名
                } else {
                    subList = copyOnWriteArrayList;
                }
                //实时向班上包括自己在内的所有在线同学广播推送成绩前三名的信息
                for (WebSocketServer item : webSocketSet) {
                    try {
                            *//*dataJb.put("num",item.number);
                            dataJb.put("headImgUrl",item.headImgUrl);
                            dataJb.put("studentName",item.studentName);
                            dataJb.put("duration",item.duration);*//*
                        if (subList.contains(item)) { //前三名中已经包含了自己
                            dataJb.put("subList", subList);
                        } else { //前三名中没有包含自己，则将自己添加进去
                            dataJb.put("subList", subList.add(item));
                        }
                        item.sendMessage(generateMessage(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_THIRD_RESPONSE, dataJb));
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }*/

        //方法一：实时广播返回前三名或者含自己的四名给前端的跳绳数量【WebSocketInfoDto】【使用的是CopyOnWriteArraySet<WebSocketServer> webSocketSet】【成功实现】
        public void broadcastJumpRopeNumber(){
            if(webSocketSet.size()>1) { //只有当前实时跳绳人数大于1时才需要广播推送
                //CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList = new CopyOnWriteArrayList<WebSocketInfoDto>();
                //查询出当前作业已经做的学生的成绩
                //CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList =geneWebSocketInfoDto();
                //先将顺序的CopyOnWriteArraySet转化为有序的CopyOnWriteArrayList【实时的学生成绩】
                //CopyOnWriteArrayList<WebSocketInfoDto> webSocketDtoList=WebSocketInfoDto.getWebSocketList(webSocketSet,historyScoreStudentList);
                CopyOnWriteArrayList<WebSocketInfoDto> webSocketDtoList=WebSocketInfoDto.getWebSocketList(webSocketSet,historyScoreStudentListStatic);
                JSONObject dataJb = new JSONObject();
                List subList = Lists.newArrayList();

                if (!CollectionUtils.isEmpty(webSocketDtoList) && webSocketDtoList.size() > 3) {
                    subList = webSocketDtoList.subList(0, 3);//再获取前三名
                } else {
                    subList = webSocketDtoList;
                }
                //实时向班上包括自己在内的所有在线同学广播推送成绩前三名的信息
                for (WebSocketServer item : webSocketSet) {
                    try {
                        /*if (subList.contains(WebSocketInfoDto.generateWebSocketInfoDto(item))) { //前三名中已经包含了自己
                            dataJb.put("subList", subList);
                        } else { //前三名中没有包含自己，则将自己添加进去
                            subList.add(WebSocketInfoDto.generateWebSocketInfoDto(item));
                            dataJb.put("subList", subList);
                        }*/

                        if (!subList.contains(WebSocketInfoDto.generateWebSocketInfoDto(this))) { //前三名中没有包含自己，则将自己添加进去
                            subList.add(WebSocketInfoDto.generateWebSocketInfoDto(item));
                        }
                        dataJb.put("subList", subList);

                        item.sendMessage(generateMessage(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_THIRD_RESPONSE, dataJb));
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }

        //方法二：实时广播返回前三名或者含自己的四名给前端的跳绳数量【WebSocketInfoDto】【使用的是ConcurrentHashMap<String, WebSocketServer> webSocketMap】【实现不准确】
        public void broadcastJumpRopeNumberMap(){
            //先将顺序的CopyOnWriteArraySet转化为有序的CopyOnWriteArrayList【实时的学生成绩】
            Collection<WebSocketServer> webSocketMapCollection=webSocketMap.values();
            CopyOnWriteArraySet<WebSocketServer> webSocketSet1 = new CopyOnWriteArraySet<WebSocketServer>(webSocketMapCollection);
            if(webSocketSet1.size()>1) { //只有当前实时跳绳人数大于1时才需要广播推送
                //CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList = new CopyOnWriteArrayList<WebSocketInfoDto>();
                //查询出当前作业已经做的学生的成绩
                //CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList =geneWebSocketInfoDto();
                //CopyOnWriteArrayList<WebSocketInfoDto> webSocketDtoList=WebSocketInfoDto.getWebSocketList(webSocketSet1,historyScoreStudentList);
                CopyOnWriteArrayList<WebSocketInfoDto> webSocketDtoList=WebSocketInfoDto.getWebSocketList(webSocketSet1,historyScoreStudentListStatic);

                JSONObject dataJb = new JSONObject();
                List subList = Lists.newArrayList();

                if (!CollectionUtils.isEmpty(webSocketDtoList) && webSocketDtoList.size() > 3) {
                    subList = webSocketDtoList.subList(0, 3);//再获取前三名
                } else {
                    subList = webSocketDtoList;
                }
                //实时向班上包括自己在内的所有在线同学广播推送成绩前三名的信息
                for (WebSocketServer item : webSocketSet1) {
                    try {
                            /*if (subList.contains(WebSocketInfoDto.generateWebSocketInfoDto(item))) { //前三名中已经包含了自己
                                dataJb.put("subList", subList);
                            } else { //前三名中没有包含自己，则将自己添加进去
                                subList.add(WebSocketInfoDto.generateWebSocketInfoDto(item));
                                dataJb.put("subList", subList);
                            }*/

                        if (!subList.contains(WebSocketInfoDto.generateWebSocketInfoDto(this))) { //前三名中没有包含自己，则将自己添加进去
                            subList.add(WebSocketInfoDto.generateWebSocketInfoDto(item));
                        }
                        dataJb.put("subList", subList);

                        item.sendMessage(generateMessage(WebsocketEnum.ACCEPT_JUMP_ROPE_NUM_THIRD_RESPONSE, dataJb));
                    } catch (IOException e) {
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }

        //学生刚刚打开做作业界面时返回前三名给前端的跳绳数量【WebSocketInfoDto】【使用的是CopyOnWriteArraySet<WebSocketServer> webSocketSet】【无需广播推送】
        public void initPushJumpRopeNumber(){
            if(webSocketSet.size()>1) { //只有当前实时跳绳人数大于1时才需要广播推送
                //查询出当前作业已经做的学生的成绩
                //CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList =geneWebSocketInfoDto();
                //先将顺序的CopyOnWriteArraySet转化为有序的CopyOnWriteArrayList【实时的学生成绩】
                //CopyOnWriteArrayList<WebSocketInfoDto> webSocketDtoList=WebSocketInfoDto.getWebSocketList(webSocketSet,historyScoreStudentList);
                CopyOnWriteArrayList<WebSocketInfoDto> webSocketDtoList=WebSocketInfoDto.getWebSocketList(webSocketSet,historyScoreStudentListStatic);
                JSONObject dataJb = new JSONObject();
                List subList = Lists.newArrayList();

                if (!CollectionUtils.isEmpty(webSocketDtoList) && webSocketDtoList.size() > 3) {
                    subList = webSocketDtoList.subList(0, 3);//再获取前三名
                } else {
                    subList = webSocketDtoList;
                }
                try {
                    dataJb.put("subList", subList);
                    this.sendMessage(generateMessage(WebsocketEnum.RANK_RESPONSE, dataJb));
                } catch (IOException e) {
                    e.printStackTrace();
            }
            }
        }

        //查询出当前作业已经做的学生的成绩
        public CopyOnWriteArrayList<WebSocketInfoDto> geneWebSocketInfoDto() {
            CopyOnWriteArrayList<WebSocketInfoDto> historyScoreStudentList = new CopyOnWriteArrayList<WebSocketInfoDto>();
            if(StringUtils.isNotEmpty(workId)) {
                List<com.wkbb.sports.dto.WebSocketInfoDto> studentScoreOfWorkList = workService.getStudentScoreOfWork(workId);
                //将com.wkbb.sports.dto.WebSocketInfoDto转化为com.wkbb.sports.websocket.WebSocketInfoDto
                historyScoreStudentList = WebSocketInfoDto.transferWebSocketInfoDto(studentScoreOfWorkList);
                //return WebSocketInfoDto.transferWebSocketInfoDto(studentScoreOfWorkList);
                log.info("====查询出当前作业已经做的学生的成绩：=个数========" + historyScoreStudentList.size() + "=======详情==========:" + JSON.toJSONString(historyScoreStudentList));
            }
            return historyScoreStudentList;
        }

        /**
         * 定时查询查询出当前作业已经做的学生的成绩，第一次延时10分钟查询，然后每隔10分钟一次
         */
        public void scheduleQueryWork(){
            sensorService = Executors.newSingleThreadScheduledExecutor();
            sensorFuture = sensorService.scheduleWithFixedDelay(new Runnable() {
                    @Override
                    public void run() {
                        int original=historyScoreStudentListStatic.size();

                        //查询出当前作业已经做的学生的成绩
                        historyScoreStudentListStatic = geneWebSocketInfoDto();
                        log.info("========scheduleQueryWork()====定时查询查询出当前作业已经做的学生的成绩=====相比较10分钟之前多出===:"+(original-historyScoreStudentListStatic.size())+"条数据！");
                    }
                }, 10, 10, TimeUnit.MINUTES);
        }

        public void close(){
            if(sensorService != null){
                log.info("=======================Shutting down connection sensor=======");
                sensorService.shutdownNow();
                sensorService = null;
            }
        }

        public static synchronized int getOnlineCount() {
            return onlineCount;
        }

        //并发计数不准确，线程不安全，不如： webSocketSet.size()
        public static synchronized void addOnlineCount() {
            WebSocketServer.onlineCount++;
        }

        public static synchronized void subOnlineCount(){
            WebSocketServer.onlineCount--;
        }

        public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
            return webSocketSet;
        }

        //该方法的返回值0代表相等，正数表示大于，负数表示小于
        //排序：优先按跳绳数量倒序排序
        /*@Override
        public int compareTo(WebSocketServer o){
            int i =  o.getNumber()-this.getNumber() ;//优先按照跳绳数量倒序排序
            if(i == 0){
                return  this.getDuration()-o.getDuration();//如果跳绳数量相等了再用跳绳时间进行排序，时间越短越靠前
            }
            return i;
        }*/

}
